﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using PloobsEngine.Commands;
using PloobsEngine.Input;
using PloobsEngine.Publishers;
using PloobsEngine.SceneControl;
using PloobsEngine.Cameras;
using PloobsEngine.Physics;
using PloobsEngine.Components;

namespace PloobsEngine.Features
{    
    public class Picking : BasicComponent
    {        
        
        private IWorld world;
        private ICamera cam;
        private Ray ray;
        private Dictionary<MouseButtons, ISubject> _subs = new Dictionary<MouseButtons, ISubject>();
        private InputPlaybleMouseBottom pbLeft =null;
        private InputPlaybleMouseBottom pbRight = null;
        private SegmentInterceptMethod  leftButtonIntercept = SegmentInterceptMethod.NO_TRINAGLEMESH;
        private SegmentInterceptMethod  rightButtonIntercept = SegmentInterceptMethod.NO_TRINAGLEMESH;
        private SegmentInterceptMethod noneButtonIntercept = SegmentInterceptMethod.NO_TRINAGLEMESH;
        private float pickingRayDistance = 500;

        public float PickingRayDistance
        {
            get { return pickingRayDistance; }
            set { pickingRayDistance = value; }
        }
        
        public override ComponentType ComponentType
        {
            get { return ComponentType.UPDATEABLE; }
        }
        public override string getMyName()
        {
            return "Picking";
        }

        public override void Update(GameTime gt)
        {
            if (_subs[MouseButtons.None].ObserverNumber() != 0)
            {
                MouseState ms = Mouse.GetState();
                UpdatePickRay(ms);

                RayTestInfo rti = world.SegmentIntersect(ray, noneButtonIntercept, pickingRayDistance);
                if (rti == null)
                    return;
                _subs[MouseButtons.None].Notify(rti);                    
               
            }

            base.Update(gt);
        }

        private void UpdatePickRay(MouseState ms)
        {            
            Matrix projection = cam.Projection;
            Matrix viewProjection = cam.View * cam.Projection;
            Matrix viewInverse = Matrix.Invert(cam.View);
            Matrix projectionInverse = Matrix.Invert(cam.Projection);
            Matrix viewProjectionInverse = projectionInverse * viewInverse;
                        
            Vector3 v;
            v.X = (((2.0f * ms.X) / EngineStuff.Viewport.Width) - 1);
            v.Y = -(((2.0f * ms.Y) / EngineStuff.Viewport.Height) - 1);
            v.Z = 0.0f;

            Ray pickRay = new Ray();
            pickRay.Position.X = viewInverse.M41;
            pickRay.Position.Y = viewInverse.M42;
            pickRay.Position.Z = viewInverse.M43;
            pickRay.Direction = Vector3.Normalize(
            Vector3.Transform(v, viewProjectionInverse) - pickRay.Position);
            this.ray = pickRay;
        }
        public void RemoveObserver(IList<IObserver> obs , MouseButtons mb)
        {
            foreach (IObserver o in obs)
            {
                _subs[mb].Dettach(o);    
            }
            
        }


        public void SetParameters(ICamera cam, IWorld world ,IList<IObserver> obs, MouseButtons mb,SegmentInterceptMethod method)
        {
            this.cam = cam;
            this.world = world;

            if (mb == MouseButtons.LeftButton)
            {
                this.leftButtonIntercept = method;

                foreach (IObserver o in obs)
                {
                    _subs[mb].Attach(o);    
                }

                if (pbLeft == null)
                {
                    pbLeft = new SimpleConcreteMouseBottomInputPlayable(StateKey.PRESS, EntityType.COMPONENT, MouseButtons.LeftButton, MouseBottomLeft,InputMask.GPICKING);
                    BindMouseCommand bmc = new BindMouseCommand(pbLeft, BindAction.ADD);
                    CommandProcessor.getCommandProcessor().SendCommandAssyncronous(bmc);
                }             
            }
            else if (mb == MouseButtons.RightButton)
            {
                this.rightButtonIntercept = method;
                foreach (IObserver o in obs)
                {
                    _subs[mb].Attach(o);
                }
                if (pbRight == null)
                {
                    pbRight = new SimpleConcreteMouseBottomInputPlayable(StateKey.PRESS, EntityType.COMPONENT, MouseButtons.RightButton, MouseBottomRight, InputMask.GPICKING);
                    BindMouseCommand bmc = new BindMouseCommand(pbRight, BindAction.ADD);
                    CommandProcessor.getCommandProcessor().SendCommandAssyncronous(bmc);

                }
            }
            else if (mb == MouseButtons.None)
            {
                this.noneButtonIntercept = method;
                foreach (IObserver o in obs)
                {
                    _subs[mb].Attach(o);
                }
            }
            else
            {
                throw new Exception("yet implemented");
            }
        

        }


        private void MouseBottomLeft(MouseState ms)
        {
               UpdatePickRay(ms);

               RayTestInfo rti = world.SegmentIntersect(ray, leftButtonIntercept, pickingRayDistance);            
                if(rti ==null)
                    return;                
               _subs[MouseButtons.LeftButton].Notify(rti);                    
                
        }

        private void MouseBottomRight(MouseState ms)
        {            
                UpdatePickRay(ms);

                RayTestInfo rti = world.SegmentIntersect(ray, rightButtonIntercept, pickingRayDistance);
                if (rti == null)
                    return;
                _subs[MouseButtons.RightButton].Notify(rti);                    
            }
        

        public override void LoadContent(EngineStuff engine)
        {
            _subs[MouseButtons.RightButton] = new ConcreteSubject();
            _subs[MouseButtons.LeftButton] = new ConcreteSubject();
            _subs[MouseButtons.None] = new ConcreteSubject();
            //_subs[MouseButtons.MiddleButton] = new ConcreteSubject();
            //_subs[MouseButtons.XButton1] = new ConcreteSubject();
            //_subs[MouseButtons.XButton2] = new ConcreteSubject();
        }
        
        public static readonly String MyName = "Picking";
    }
}
